﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Resources;
using System.Xml;
using Tum.CollabXT.TFS.Resources;
using System.Collections;

namespace Tum.CollabXT.TFS
{
    internal class TemplateProcessor
    {
        /// <summary>
        /// Dictionary to resolve the translation from a template processor type name to its type.
        /// </summary>
        internal static readonly Dictionary<string, ProcessorType> ProcessorNameToTypeDictionary = new Dictionary<string, ProcessorType>();

        /// <summary>
        /// Gets the used TFS tool provider.
        /// </summary>
        public TFSToolProvider ToolProvider
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the belonging process provider.
        /// </summary>
        public IProcessProvider ProcessProvider
        {
            get
            {
                return ToolProvider.ProcessProvider;
            }
        }

        /// <summary>
        /// Gets a dictionary of localized strings to be used within the provider.
        /// </summary>
        public Dictionary<string, string> LanguageDictionary
        {
            get;
            private set;
        }


        /// <summary>
        /// Gets or sets the name of the resource file that contains the template localizations.
        /// </summary>
        public string LanguageFileName
        {
            get;
            internal set;
        }

        /// <summary>
        /// Gets or sets the name of the TFS template folder contained in the provider's template.
        /// </summary>
        public string InnerTemplateFolder
        {
            get;
            internal set;
        }

        /// <summary>
        /// Name of the TFS process template configuration file.
        /// </summary>
        const string ProcessTemplateFile = "ProcessTemplate.xml";

        /// <summary>
        /// Gets the configuration document for the template.
        /// </summary>
        public XmlDocument TemplateConfigDocument
        {
            get;
            private set;
        }

        /// <summary>
        /// Static constructor. Builds the processor name to type dictionary.
        /// </summary>
        static TemplateProcessor()
        {
            ProcessorNameToTypeDictionary.Add("classification", ProcessorType.Classification);
            ProcessorNameToTypeDictionary.Add("groups", ProcessorType.Groups);
            ProcessorNameToTypeDictionary.Add("portal", ProcessorType.Portal);
            ProcessorNameToTypeDictionary.Add("workitemtracking", ProcessorType.WorkItemTracking);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="tfsTP">Tool provider to be used. TFSToolProvider.TemplatePath must end with \\ or /.</param>
        public TemplateProcessor(TFSToolProvider tfsTP)
        {
            if (tfsTP == null)
                throw new ArgumentNullException("tfsTP");
            ToolProvider = tfsTP;
        }

        /// <summary>
        /// Checks if all data specified by the tool provider is valid.
        /// </summary>
        private void CheckProviderData()
        {
            if (!Directory.Exists(ToolProvider.TemplatePath))
                throw new DirectoryNotFoundException("TemplatePath");
            if (!File.Exists(ToolProvider.TemplatePath + LanguageFileName))
                throw new FileNotFoundException(LanguageFileName);
            if (!Directory.Exists(ToolProvider.TemplatePath + InnerTemplateFolder))
                throw new DirectoryNotFoundException("InnerTemplateFolder");
            if (!File.Exists(ToolProvider.TemplatePath + InnerTemplateFolder + "/" + ProcessTemplateFile))
                throw new FileNotFoundException(ProcessTemplateFile);
        }

        /// <summary>
        /// Creates the final TFS process template.
        /// </summary>
        /// <param name="templateConfigDocument">Config document used for this template.</param>
        public void Process(XmlDocument templateConfigDocument)
        {
            if (templateConfigDocument == null)
                throw new ArgumentNullException("templateConfigDocument");
            TemplateConfigDocument = templateConfigDocument;

            //Check input data
            CheckProviderData();
            

            //Load language file
            LanguageDictionary = LoadLanguageDictionary(ToolProvider.TemplatePath + LanguageFileName);

            //Create output dictionary
            Directory.CreateDirectory(ToolProvider.OutputPath);


            #region Copy template
            ToolProvider.ProcessLog.AddEntry(Language.Processing_OutputDirectory);
            //Clean output directory
            try
            {
                Helper.DeleteDirectory(ToolProvider.OutputPath);
            }
            catch
            {
                ToolProvider.ProcessLog.AddEntry(Language.Error_DeletingOutputDirectory, LogEntryType.Error);
                return;
            }

            try
            {
                //Copy template data to output directory
                Helper.CopyDirectory(ToolProvider.TemplatePath + "tpl/", ToolProvider.OutputPath);
            }
            catch (FileCopyException e)
            {
                ToolProvider.ProcessLog.AddEntry(string.Format(Language.Template_FileCopyFailed, e.SourcePath, e.DestinationPath), LogEntryType.Error);
                return;
            }
            catch (Exception e)
            {
                throw new TFSException(string.Format(Language.Error_Unexpected, e.Message, e.StackTrace));
            }
            #endregion

            //Build template processors list
            List<IProcessor> templateProcessors = new List<IProcessor>();
            var templateProcessorNodes = templateConfigDocument.SelectNodes("//Tasks/Task");

            //Order list (e.g. workitems must be before classification)
            var orderedTemplateProcessorNodesList = new SortedList<ProcessorType, XmlNode>(new ProcessorOrderComparer());
            foreach (XmlNode curProcessorNode in templateProcessorNodes)
            {
                string curProcessorType = curProcessorNode.Attributes["type"].Value.ToLower();
                if (!ProcessorNameToTypeDictionary.ContainsKey(curProcessorType))
                    throw new TemplateConfigInvalidException("Unknown Task type");
                orderedTemplateProcessorNodesList.Add(ProcessorNameToTypeDictionary[curProcessorType], curProcessorNode);
            }

            //First processor is the process template processor
            templateProcessors.Add(new ProcessTemplate());

            //Create processor instance list
            WorkItems workItemProcessor = null;
            foreach (var curProcessorEntry in orderedTemplateProcessorNodesList)
            {
                IProcessor curProcessor = null;

                switch (curProcessorEntry.Key)
                {
                    case ProcessorType.WorkItemTracking:
                        workItemProcessor = new WorkItems();
                        curProcessor = workItemProcessor; // save work item processor for classification
                        break;

                    case ProcessorType.Portal:
                        curProcessor = new WSS();
                        break;

                    case ProcessorType.Groups:
                        curProcessor = new GroupsAndPermissions();
                        break;

                    case ProcessorType.Classification:
                        if (workItemProcessor == null)
                            throw new TFSException("Internal error");
                        curProcessor = new Classification(workItemProcessor);
                        break;
                }
                if (curProcessor == null)
                    throw new TFSException("Internal error"); //No processor created. Must not happen

                //Complete processor initialization and add it to list
                curProcessor.TaskFileName = curProcessorEntry.Value.Attributes["file"].Value;
                curProcessor.TaskFileFolder = curProcessorEntry.Value.Attributes["folder"].Value;

                templateProcessors.Add(curProcessor);
            }

            try
            {
                //Run processors
                foreach (IProcessor curProcessor in templateProcessors)
                {
                    ToolProvider.ProcessLog.AddSeparator();
                    if (!curProcessor.Process(this))
                    {
                        //Processing failed
                        if (ToolProvider.ProcessLog.ErrorCount == 0)
                        {
                            //Error not set by processor. Add it.
                            ToolProvider.ProcessLog.AddEntry(string.Format(Language.Error_Unexpected, curProcessor.GetType()), LogEntryType.Error);
                            return;
                        }
                    }
                }
                
                ToolProvider.ProcessLog.AddSeparator();
                ToolProvider.ProcessLog.AddEntry(Language.Processing_CompletedSuccessfully);
            }
            catch (Exception e)
            {
                throw new TFSException(string.Format(Language.Error_Unexpected, e.Message, e.StackTrace));
            }
        }


        /// <summary>
        /// Loads a dictionary from a language resource file.
        /// </summary>
        /// <param name="languageResourceFile">The .resources file's path.</param>
        /// <returns>Dictionary with all language ent</returns>
        private static Dictionary<string, string> LoadLanguageDictionary(string languageResourceFile)
        {
            try
            {
                using (ResourceReader reader = new ResourceReader(languageResourceFile))
                {
                    var dict = new Dictionary<string, string>();
                    foreach (DictionaryEntry entry in reader)
                    {
                        string key = entry.Key as string;
                        if (key == null)
                            continue;
                        dict.Add(key, entry.Value as string);
                    }
                    return dict;
                }
            }
            catch (Exception)
            {
                return null;
            }
        }
    }

    internal class ProcessorOrderComparer : IComparer<ProcessorType>
    {
        public int Compare(ProcessorType x, ProcessorType y)
        {
            if(x == y)
                return 0;

            //WorkItemTracking must always be first, classification must always be last
            if (x == ProcessorType.WorkItemTracking || y == ProcessorType.Classification)
                return -1;
            if (y == ProcessorType.WorkItemTracking || x == ProcessorType.Classification)
                return 1;

            //In all other cases order does not matter
            return x.CompareTo(y);
        }
    }

    internal enum ProcessorType
    {
        ProcessTemplate,
        Classification,
        Groups,
        Portal,
        WorkItemTracking,
    }
}
